/*  RawEthernet Application
 *  Written by: Jeremiah Clark, Oct 2003
 *  This code is opensouce and free for you to use.  All I ask is 
 *  that you give credit in your source comments.
 * 
 *  This application will interface with the NDIS Protocol Driver to enable
 *  the sending of raw ethernet packets out of a network device.
 */

/* 
 * Some addition and modification add by Dimitry Kaplin
 * - opening async device
 * - minor
 */

using System;
using System.Runtime.InteropServices;	// For DllImport
using System.Text;
using System.Collections.Generic;						// For Encoding

namespace RawEthernet
{
	// Class to perform the sending of raw Ethernet packets
	public class RawEthernet
	{
		#region ATTRIBUTES
			
			// Path to the NDIS Protocol Driver so we can open it like a file
            private string m_sNdisProtDriver = "\\\\.\\\\NdisProt";

			// IntegerPointer to hold the handle of the driver
			private IntPtr m_iHandle = IntPtr.Zero;

			// Bool to hold whether we have a connection to the driver
			private bool m_bDriverOpen = false;

			// Bool to hold whether we are bound to an adapter
			private bool m_bAdapterBound = false;

		#endregion ATTIBUTES

		#region PROPERTIES

			// public properties for the class
			public IntPtr Handle { get { return this.m_iHandle; } }
			public bool IsDriverOpen { get { return this.m_bDriverOpen; } }
			public bool IsAdapterBound { get { return this.m_bAdapterBound; } }

		#endregion PROPERTIES

		#region CONSTRUCTOR

			public RawEthernet()
			{
				// Open a handle to the NDIS Device driver
				this.m_bDriverOpen = this.OpenDriver();
			}

		#endregion CONSTRUCTOR

		#region METHODS

			// method to open a handle to the driver so we can access it
			// returns true if we get a valid handle, false if otherwise
			private bool OpenDriver()
			{
				// User the CreateFile API to open a handle to the file
				this.m_iHandle = CreateFile(this.m_sNdisProtDriver, 
					GENERIC_WRITE|GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, 0);

				// Check to see if we got a valid handle
				if ((int)this.m_iHandle <= 0)
				{
					// If not, then return false and reset the handle to 0
					this.m_iHandle = IntPtr.Zero;
					return false;
				}

				// Otherwise we have a valid handle
				return true;
			}


			// method to return an array of the active Network adapters on your system
			public AdaptersInfo[] EnumerateAdapters()
			{
				bool validAdapter = true;	// are we still getting a valid adapter

				// we are going to look for up to 10 adapters
				// temp array to hold the adapters that we find
				List<AdaptersInfo> aiTemp = new List<AdaptersInfo>(); 

				//start a loop while we look for adapters one by one starting at index 0
				do
				{
					// buffer to hold the adapter information that we get
					byte[] buf = new byte[1024];
					// uint to hold the number of bytes that we read
					uint iBytesRead = 0;
					// NDISPROT_QUERY_BINDING structure containing the index
					// that we want to query for
					NDISPROT_QUERY_BINDING ndisprot = new NDISPROT_QUERY_BINDING();
                    ndisprot.BindingIndex = (ulong)aiTemp.Count;
					// uint to hold the length of the ndisprot
					uint bufsize = (uint)Marshal.SizeOf(ndisprot);	
					// perform the following in and unsafe context
					unsafe
					{
						// create a void pointer to buf
						fixed (void* vpBuf = buf)
						{
							// use the DeviceIoControl API to query the adapters
							validAdapter = DeviceIoControl(this.m_iHandle,
								IOCTL_NDISPROT_QUERY_BINDING, (void*)&ndisprot, 
								bufsize, vpBuf, (uint)buf.Length, 
								&iBytesRead, 0);
						}
					}
					// if DeviceIoControl returns false, then there are no
					// more valid adapters, so break the loop
					if (!validAdapter) break;

					// add the adapter information to the temp AdaptersInfo struct array
					// first, get a string containing the info from buf
					string tmpinfo = Encoding.Unicode.GetString(buf).Trim((char)0x00);
					tmpinfo = tmpinfo.Substring(tmpinfo.IndexOf("\\D"));
					// add the info to aiTemp
                    string id = tmpinfo.Substring(0, tmpinfo.IndexOf("}") + 1);
                                        
                    string name = tmpinfo.Substring(tmpinfo.IndexOf("}")+1).Trim((char)0x00);
					aiTemp.Add(new AdaptersInfo(aiTemp.Count,id,name));
                    
				// loop while we have a valid adapter
				}while (validAdapter);	
				
				
				// return aiReturn struct
				return aiTemp.ToArray();
			}


			// method to bind an adapter to the a the handle that we have open
			public bool BindAdapter(string adapterID)
			{
				// char array to hold the adapterID string
				char[] ndisAdapter = new char[256];
				// convert the string to a unicode non-localized char array
				int iNameLength = 0, i = 0;
				for (i=0;i<adapterID.Length;i++)
				{
					ndisAdapter[i] = adapterID[i];
					iNameLength++;
				}
				ndisAdapter[i] = '\0';
				
				// uint to hold the number of bytes read from DeviceIoControl
				uint uiBytesReturned;
			
				// do the following in an unsafe context
				unsafe 
				{
					// create a void pointer to ndisAdapter
					fixed (void* vpNdisAdapter = &ndisAdapter[0])
					{
						// Call the DeviceIoControl API to bind the adapter to the handle
						return DeviceIoControl(this.m_iHandle, IOCTL_NDISPROT_OPEN_DEVICE,
							vpNdisAdapter, (uint)(iNameLength*sizeof(char)), null, 0, 
							&uiBytesReturned, 0);
					}
				}
			}


			// method to close the handle to the device driver
			public bool CloseDriver()
			{
                if (m_iHandle == IntPtr.Zero)
                {
                    // not opened or already closed
                    return true;
                }

                CancelIo(this.m_iHandle);
                if (CloseHandle(this.m_iHandle))
                {
                    m_iHandle = IntPtr.Zero;

                    m_bDriverOpen = false;

                    m_bAdapterBound = false;

                    return true;
                }
                return false;
			}


			// method to write a packet of bytes to the adapter
			public bool DoWrite(byte[] packet)
			{
				// bool to hold whether the packet was sent or not
				bool packetSent = false;

                // uint to hol the number of bytes sent
                uint uiSentCount = 0;
                unsafe
                {
                    fixed (void* pBuf = &packet[0])
                    {
                        packetSent = WriteFileWrapper((uint)this.m_iHandle,
                                pBuf,
                                (uint)packet.Length,
                                out uiSentCount);
                    }
                }
                
				// check to see if packet was sent
				if (!packetSent)
				{
					Console.WriteLine("ERROR: Packet not sent: 0 bytes written");
					return false;
				}

				// otherwise the packet was sent
				Console.WriteLine("Packet sent: " + uiSentCount.ToString() + "bytes written");
				return true;
			}

            // method to read a buffer of bytes from the adapter
            public bool DoRead(ref byte[] packet, out ulong readBytes)
            {
                readBytes = 0;
                if (packet == null)
                    return false;
                if (packet.Length == 0)
                    return false;

                unsafe
                {
                    fixed (void* pBuf = &packet[0])
                    {
                        readBytes = (ulong)ReadFileWrapper((uint)this.m_iHandle,
                                pBuf,
                                (uint)packet.Length);
                    }
                }

                // check to see if packet was sent
                if (readBytes <= 0)
                {
                    return false;
                }

                return true;
            }

		#endregion METHODS

		#region CONSTANTS

			// file access constants
			private const uint GENERIC_READ  = 0x80000000;
			private const uint GENERIC_WRITE = 0x40000000;
			
			// file creation disposition constant
			private const uint OPEN_EXISTING = 0x00000003;

			// file attributes constant
			private const uint FILE_ATTRIBUTE_NORMAL = 0x00000080;
            private const uint FILE_FLAG_OVERLAPPED  = 0x40000000;

			// invalid handle constant
			private const int INVALID_HANDLE_VALUE = -1;

			// iocontrol code constants
			private const uint IOCTL_NDISPROT_QUERY_BINDING = 0x12C80C;
			private const uint IOCTL_NDISPROT_OPEN_DEVICE   = 0x12C800;

		#endregion CONSTANTS

		#region IMPORTS

			[DllImport("kernel32", SetLastError=true)]
			private static extern IntPtr CreateFile(
				string _lpFileName,				// filename to open
				uint _dwDesiredAccess,			// access permissions for the file
				uint _dwShareMode,				// sharing or locked
				uint _lpSecurityAttributes,		// security attributes
				uint _dwCreationDisposition,	// file creation method (new, existing)
				uint _dwFlagsAndAttributes,		// other flags and sttribs
				uint _hTemplateFile);			// template file for creating

			[DllImport("kernel32", SetLastError=true)]
			private static extern unsafe bool WriteFile(
				IntPtr _hFile,					// handle of the file to write to
				void* _lpBuffer,				// pointer to the buffer to write
				uint _nNumberOfBytesToWrite,	// number of bytes to write from the buffer
				uint* _lpNumberOfBytesWritten,	// [out] number of bytes written to the file
				uint _lpOverlapped);			// used for async reading and writing

            [DllImport("SharpWinApiWrapper.dll")]
            static extern unsafe bool WriteFileWrapper(uint hFile, void* lpBuffer,
               uint nNumberOfBytesToWrite, out uint lpNumberOfBytesWritten);

            [DllImport("SharpWinApiWrapper.dll")]
            static extern unsafe int ReadFileWrapper(uint hFile, void* lpBuffer,
               uint bufferLen);

			[DllImport("kernel32", SetLastError=true)]
			private static extern bool CloseHandle(
				IntPtr _hObject);				// handle for the object to close

			[DllImport("kernel32", SetLastError=true)]
			private static extern unsafe bool DeviceIoControl(
				IntPtr _hDevice,				// handle of the device
				uint _dwIoControlCode,			// IO control code to execute
				void* _lpInBuffer,				// Input buffer for the execution
				uint _nInBufferSize,			// size of the input buffer
				void* lpOutBuffer,				// [out] output buffer for the execution
				uint _nOutBufferSize,			// [size of the output buffer
				uint* _lpBytesReturned,			// [out] number of bytes returned
				uint _lpOverlapped);			// used for async reading and writing

            [DllImport("kernel32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            static extern bool CancelIo(IntPtr hFile);

		#endregion IMPORTS

		#region STRUCTS

		    [StructLayout(LayoutKind.Sequential)]
			private struct NDISPROT_QUERY_BINDING
			{
				public ulong BindingIndex;        // 0-based binding number
				public ulong DeviceNameOffset;    // from start of this struct
				public ulong DeviceNameLength;    // in bytes
				public ulong DeviceDescrOffset;    // from start of this struct
				public ulong DeviceDescrLength;    // in bytes
			}

		#endregion STRUCTS
	}
	
	// Structure to hold the information for a specific adapter
	public class AdaptersInfo: Object
	{
		#region ATTRIBUTES

			private int m_iIndex;			// The index of the adapter
			private string m_sAdapterID;	// The ID of the adapter
			private string m_sAdapterName;	// The name of the adapter

		#endregion ATTRIBUTES

		#region PROPERTIES

			public int Index {get{return m_iIndex;}set{m_iIndex = value;}}
			public string AdapterID {get{return m_sAdapterID;}set{m_sAdapterID = value;}}
			public string AdapterName {get{return m_sAdapterName;}set{m_sAdapterName = value;}}

		#endregion PROPERTIES

		#region CONSTRUCTOR

			// The constructor for this struct accepts three arguments
			//  index = the adapter index
			//  adapterID = the ID of the adapter
			//  adapterName = the name of the adapter
			public AdaptersInfo(int index, string adapterID, string adapterName)
			{
				// set the attributes according to the passed args
				this.m_iIndex = index;
				this.m_sAdapterID = adapterID;
				this.m_sAdapterName = adapterName;
			}

		#endregion CONSTRUCTOR

		#region METHODS

		public override string ToString()
		{
			return (this.m_iIndex +" - " + this.m_sAdapterName).Replace("\r\n",".");
		}

        public string GetFullInfoString()
        {
            return this.m_iIndex + ". " + this.m_sAdapterID + " - " + this.m_sAdapterName;
        }


		#endregion METHODS
	}
}
